﻿using System;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Diagnostics;
using Microsoft.Phone.Controls;

namespace SStuff.Pivot
{
    [TemplatePart(Name = "PART_Container0", Type = typeof(ContentPresenter))]
    [TemplatePart(Name = "PART_Container1", Type = typeof(ContentPresenter))]
    public class PivotItemsControl : ItemsControl
    {

        public PivotItemsControl()
        {
            DefaultStyleKey = typeof (PivotItemsControl);
            ManipulationStarted += new EventHandler<ManipulationStartedEventArgs>(PivotItemsControl_ManipulationStarted);
            ManipulationDelta += new EventHandler<ManipulationDeltaEventArgs>(PivotItemsControl_ManipulationDelta);
            ManipulationCompleted += new EventHandler<ManipulationCompletedEventArgs>(PivotItemsControl_ManipulationCompleted);
        }

        public void ManipulateStartExternally(ManipulationStartedEventArgs e)
        {
            PivotItemsControl_ManipulationStarted(null, e);
        }
        public void ManipulateDeltaExternally(ManipulationDeltaEventArgs e)
        {
            PivotItemsControl_ManipulationDelta(null, e);
        }
        public void ManipulateCompletedExternally(ManipulationCompletedEventArgs e)
        {
            PivotItemsControl_ManipulationCompleted(null, e);
        }

        private ContentPresenter _container0, _container1;
        private ContentPresenter[] _containers;
        private TranslateTransform[] _containersTransforms;
        private int _currentContainer = 0;



        public bool HandlesAlreadyHandledManipulations
        {
            get { return (bool)GetValue(HandlesAlreadyHandledManipulationsProperty); }
            set { SetValue(HandlesAlreadyHandledManipulationsProperty, value); }
        }

        // Using a DependencyProperty as the backing store for HandlesAlreadyHandledManipulations.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty HandlesAlreadyHandledManipulationsProperty =
            DependencyProperty.Register("HandlesAlreadyHandledManipulations", typeof(bool), typeof(PivotItemsControl), new PropertyMetadata(false));



        public override void OnApplyTemplate()
        {
            base.OnApplyTemplate();
            _container0 = GetTemplateChild("PART_Container0") as ContentPresenter;
            _container1 = GetTemplateChild("PART_Container1") as ContentPresenter;
            if(_container0 == null || _container1 == null)
                throw new InvalidOperationException("PART_Container0 or PART_Container1 is missing");
            _containers = new ContentPresenter[]{_container0, _container1};

            _containersTransforms = new TranslateTransform[] { new TranslateTransform(), new TranslateTransform() };
            _containers[0].RenderTransform = _containersTransforms[0];
            _containers[1].RenderTransform = _containersTransforms[1];

            var current = _containers[_currentContainer % 2];
            var other = _containers[(_currentContainer + 1)%2];
            current.Content = SelectedItem;
            current.Opacity = 1;
            current.IsHitTestVisible = true;
            other.Opacity = 0;
            other.IsHitTestVisible = false;

        }

        private bool _manipulationCanPrevious;
        private bool _manipulationCanNext;

        private double _manipulationStartOffsetX;
        private double _manipulationNextOffsetX;
        private double _manipulationPreviousOffsetX;


        void PivotItemsControl_ManipulationCompleted(object sender, ManipulationCompletedEventArgs e)
        {
            if (e.Handled && ! HandlesAlreadyHandledManipulations)
                return;

            //if (HasFlowItemsControlWithin(e.OriginalSource as DependencyObject))
            //    return;



            var currentIndex = Items.IndexOf(SelectedItem);
            if (currentIndex == -1)
                return;

            EnsurePresenterElementAndAnim(ItemContainerGenerator.ContainerFromIndex(currentIndex) as PivotItem);

            if(_manipulationCanNext && CloserThan(_presenterTransform.X, _manipulationNextOffsetX, _manipulationStartOffsetX))
            {
                SelectedItem = Items[currentIndex + 1];
            }
            else if (_manipulationCanPrevious && CloserThan(_presenterTransform.X, _manipulationPreviousOffsetX, _manipulationStartOffsetX))
            {

                SelectedItem = Items[currentIndex - 1];
            }
            else
            {
                var container = ItemContainerGenerator.ContainerFromIndex(currentIndex) as PivotItem;
                var translateToContainer = _presenterElement.TransformToVisual(container);
                var offset = translateToContainer.Transform(new Point(0, 0));
                _headerStoryboard.Pause();
                _headerAnim.To = offset.X + container.Margin.Left;
                _headerStoryboard.Begin();

                var backOpacityAnim = new DoubleAnimation
                                          {
                                              To = 1,
                                              Duration = _headerAnim.Duration,
                                              EasingFunction = _headerAnim.EasingFunction
                                          };
                Storyboard.SetTarget(backOpacityAnim, _containers[_currentContainer % 2]);
                Storyboard.SetTargetProperty(backOpacityAnim, new PropertyPath(UIElement.OpacityProperty));

                var backTranslateAnim = new DoubleAnimation()
                                            {
                                                To = 0,
                                                Duration = _headerAnim.Duration,
                                                EasingFunction = _headerAnim.EasingFunction
                                            };

                Storyboard.SetTarget(backTranslateAnim, _containersTransforms[_currentContainer % 2]);
                Storyboard.SetTargetProperty(backTranslateAnim, new PropertyPath(TranslateTransform.XProperty));
                if(_backSb != null)
                {
                    _backSbStopper();
                }
                _backSb = new Storyboard {Children = {backOpacityAnim, backTranslateAnim}};
                _backSb.Begin();

                var currentContainerCopy = _currentContainer;
                _backSbStopper = () =>
                                     {
                                         var currentOpacity = _containers[currentContainerCopy%2].Opacity;
                                         var currentX = _containersTransforms[currentContainerCopy%2].X;
                                         _backSb.Stop();
                                         _backSb = null;
                                         _containers[currentContainerCopy%2].Opacity = currentOpacity;
                                         _containersTransforms[currentContainerCopy%2].X = currentX;
                                     };
            }
        }

        //bool HasFlowItemsControlWithin(DependencyObject eventSource)
        //{
        //    while (true)
        //    {
        //        if (eventSource == null || eventSource == this)
        //            return false;

        //        if (eventSource is FlowControls.FlowItemsControlBase)
        //            return true;

        //        eventSource = VisualTreeHelper.GetParent(eventSource);
        //    }
        //}

        private Storyboard _backSb;
        private Action _backSbStopper;

        bool CloserThan(double toCheck, double goal, double referenceValue)
        {
            var distanceToGoal = Math.Abs(goal - toCheck);
            var distanceToReference = Math.Abs(referenceValue - toCheck);
            return distanceToGoal < distanceToReference;
        }

        double Lerp(double start, double end, double amount)
        {
            return start + amount*(end - start);
        }
        
        void PivotItemsControl_ManipulationDelta(object sender, ManipulationDeltaEventArgs e)
        {
            if (e.Handled && !HandlesAlreadyHandledManipulations)
                return;

            //if (HasFlowItemsControlWithin(e.OriginalSource as DependencyObject))
            //    return;
            if (_backSb != null)
            {
                _backSbStopper();
            }
            var currentIndex = Items.IndexOf(SelectedItem);
            if(currentIndex == -1)
                return;

            EnsurePresenterElementAndAnim(ItemContainerGenerator.ContainerFromIndex(currentIndex) as PivotItem);

            var translatedManipulationTranslation = Translate(e.CumulativeManipulation.Translation);

            if (translatedManipulationTranslation.X < 0)
            {
                var progress = -translatedManipulationTranslation.X / this.ActualWidth;
                _presenterTransform.X = Lerp(_manipulationStartOffsetX, _manipulationNextOffsetX,
                                             progress);
                _containers[_currentContainer%2].Opacity = 1 - progress;
            }
            else
            {
                var progress = translatedManipulationTranslation.X / this.ActualWidth;
                _presenterTransform.X = Lerp(_manipulationStartOffsetX, _manipulationPreviousOffsetX,
                                             progress);
                _containers[_currentContainer % 2].Opacity = 1 - progress;
            }
            _containersTransforms[_currentContainer % 2].X = translatedManipulationTranslation.X;
        }

        private Point Translate(Point point)
        {
            //var owningPage = GetOwningPage();
            //if (owningPage == null)
            //    return point;
            //switch (owningPage.Orientation)
            //{
            //    case PageOrientation.Portrait:
            //    case PageOrientation.PortraitUp:
            //        return point;
            //    case PageOrientation.Landscape:
            //    case PageOrientation.LandscapeLeft:
            //        return new Point(point.Y, point.X);
            //    case PageOrientation.PortraitDown:
            //        return new Point(-point.X, -point.Y);
            //    case PageOrientation.LandscapeRight:
            //        return new Point(-point.Y, -point.X);

            //}
            return point;
        }

        PhoneApplicationPage GetOwningPage()
        {
            DependencyObject elem = this;
            while (!(elem is PhoneApplicationPage))
            {
                if (elem == null)
                    return null;
                elem = VisualTreeHelper.GetParent(elem);
            }
            return elem as PhoneApplicationPage;
        }

        void PivotItemsControl_ManipulationStarted(object sender, ManipulationStartedEventArgs e)
        {
            if (e.Handled && !HandlesAlreadyHandledManipulations)
                return;

            //if (HasFlowItemsControlWithin(e.OriginalSource as DependencyObject))
            //    return;
            var currentIndex = Items.IndexOf(SelectedItem);
            if(currentIndex == -1)
            {
                _manipulationCanPrevious = false;
                _manipulationCanNext = false;
                return;
            }
            else
            {
                EnsurePresenterElementAndAnim(ItemContainerGenerator.ContainerFromIndex(currentIndex) as PivotItem);
                _manipulationCanPrevious = currentIndex > 0;
                _manipulationCanNext = currentIndex < Items.Count - 1;
            }
            _headerStoryboard.Pause();
            _manipulationStartOffsetX = _presenterTransform.X;
            if(_manipulationCanPrevious)
            {
                var previousContainer = ItemContainerGenerator.ContainerFromIndex(currentIndex - 1) as Control;
                var translateToContainer = _presenterElement.TransformToVisual(previousContainer);
                var offset = translateToContainer.Transform(new Point(0, 0));
                _manipulationPreviousOffsetX = offset.X + previousContainer.Margin.Left;
            }
            else
            {
                _manipulationPreviousOffsetX = _manipulationStartOffsetX;
            }
            if(_manipulationCanNext)
            {
                var nextContainer = ItemContainerGenerator.ContainerFromIndex(currentIndex + 1) as Control;
                var translateToContainer = _presenterElement.TransformToVisual(nextContainer);
                var offset = translateToContainer.Transform(new Point(0, 0));
                _manipulationNextOffsetX = offset.X + nextContainer.Margin.Left;
            }
            else
            {
                _manipulationNextOffsetX = _manipulationStartOffsetX;
            }


        }
        
        


       

        public object SelectedItem
        {
            get { return (object)GetValue(SelectedItemProperty); }
            set { SetValue(SelectedItemProperty, value); }
        }

        // Using a DependencyProperty as the backing store for SelectedItem.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty SelectedItemProperty =
            DependencyProperty.Register("SelectedItem", typeof(object), typeof(PivotItemsControl), new PropertyMetadata(null, (s,a)=>((PivotItemsControl)s).OnSelectedItemChanged(a.OldValue, a.NewValue)));

        private Storyboard _headerStoryboard;
        private DoubleAnimation _headerAnim;

        private UIElement _presenterElement;
        private TranslateTransform _presenterTransform;

        private void OnSelectedItemChanged(object oldValue, object newValue)
        {
            if(object.Equals(oldValue, newValue))
                return;

            var oldIndex = Items.IndexOf(oldValue);
            var newIndex = Items.IndexOf(newValue);
            if(oldValue!=null)
            {
                var oldContainer = ItemContainerGenerator.ContainerFromItem(oldValue) as PivotItem;
                if (oldContainer != null)
                    oldContainer.IsSelected = false;
            }
            var container = ItemContainerGenerator.ContainerFromItem(SelectedItem) as PivotItem;
            if (container != null)
            {
                container.IsSelected = true;
                EnsurePresenterElementAndAnim(container);
                var translateToContainer = _presenterElement.TransformToVisual(container);
                var offset = translateToContainer.Transform(new Point(0, 0));
                _headerStoryboard.Pause();
                _headerAnim.To = offset.X + container.Margin.Left;
                _headerStoryboard.Begin();
                FlipContent(newIndex > oldIndex);
            }
            else
            {
                _containers[_currentContainer%2].Content = newValue;
            }

        }

        private void FlipContent(bool toRight)
        {
            if (_backSb != null)
            {

                _backSbStopper();
            }
            var oldContainer = _containers[_currentContainer % 2];
            var oldContainerTransform = _containersTransforms[_currentContainer % 2];

            _currentContainer++;

            var newContainer = _containers[_currentContainer % 2];
            var newContainerTransform = _containersTransforms[_currentContainer % 2];
            oldContainer.IsHitTestVisible = false;
            newContainer.IsHitTestVisible = true;
            newContainer.Content = SelectedItem;

            var oldOpacityAnim = new DoubleAnimation
            {
                To = 0,
                Duration = _headerAnim.Duration,
                EasingFunction = _headerAnim.EasingFunction
            };
            Storyboard.SetTarget(oldOpacityAnim, oldContainer);
            Storyboard.SetTargetProperty(oldOpacityAnim, new PropertyPath(UIElement.OpacityProperty));
            var oldTranslateAnim = new DoubleAnimation
            {
                To = toRight?- ActualWidth:ActualWidth,
                Duration = _headerAnim.Duration,
                EasingFunction = _headerAnim.EasingFunction
            };
            Storyboard.SetTarget(oldTranslateAnim, oldContainerTransform);
            Storyboard.SetTargetProperty(oldTranslateAnim, new PropertyPath(TranslateTransform.XProperty));
            var newOpacityAnim = new DoubleAnimation
            {
                From=0,
                To = 1,
                Duration = _headerAnim.Duration,
                EasingFunction = _headerAnim.EasingFunction
            };
            Storyboard.SetTarget(newOpacityAnim, newContainer);
            Storyboard.SetTargetProperty(newOpacityAnim, new PropertyPath(UIElement.OpacityProperty));
            var newTranslateAnim = new DoubleAnimation
            {
                From = toRight ? ActualWidth : -ActualWidth,
                To=0,
                Duration = _headerAnim.Duration,
                EasingFunction = _headerAnim.EasingFunction
            };
            Storyboard.SetTarget(newTranslateAnim, newContainerTransform);
            Storyboard.SetTargetProperty(newTranslateAnim, new PropertyPath(TranslateTransform.XProperty));

            _backSb = new Storyboard
                          {
                              Children = {oldOpacityAnim, oldTranslateAnim, newOpacityAnim, newTranslateAnim},
                              Duration = new Duration(TimeSpan.FromSeconds(1))
                          };
            
            _backSb.Begin();
            _backSb.Completed += delegate
            {
                _backSb.Stop();
                oldContainer.Opacity = 0;
                newContainer.Opacity = 1;
                newContainerTransform.X = 0;
                _backSb = null;
            };
            _backSbStopper = () =>
                                 {
                                     _backSb.Stop();
                                     oldContainer.Opacity = 0;
                                     newContainer.Opacity = 1;
                                     newContainerTransform.X = 0;
                                     _backSb = null;
                                 };
        }

        private void EnsurePresenterElementAndAnim(PivotItem container)
        {
            if (_presenterElement == null)
            {
                _presenterElement = VisualTreeHelper.GetParent(container) as UIElement;
                _presenterTransform = _presenterElement.RenderTransform as TranslateTransform;
                if (_presenterTransform == null)
                {
                    _presenterTransform = new TranslateTransform();
                    _presenterElement.RenderTransform = _presenterTransform;

                }
            }
            if(_headerStoryboard == null)
            {
                _headerAnim = new DoubleAnimation { Duration = new Duration(TimeSpan.FromSeconds(1)), EasingFunction = new CubicEase{EasingMode = EasingMode.EaseOut}};
                Storyboard.SetTarget(_headerAnim, _presenterTransform);
                Storyboard.SetTargetProperty(_headerAnim, new PropertyPath(TranslateTransform.XProperty));
                _headerStoryboard = new Storyboard { Children = { _headerAnim } };
            }
        }


        protected override bool IsItemItsOwnContainerOverride(object item)
        {
            return item is PivotItem;
        }

        protected override DependencyObject GetContainerForItemOverride()
        {
            return new PivotItem(this){HeaderTemplate = HeaderTemplate};
        }

        protected override void ClearContainerForItemOverride(DependencyObject element, object item)
        {
            var pi = element as PivotItem;
            if(pi == null)
                return;

            pi.ClearValue(PivotItem.HeaderProperty);
            pi.ClearValue(ContentControl.ContentProperty);
            pi.ClearValue(PivotItem.IsSelectedProperty);
        }



        public DataTemplate HeaderTemplate
        {
            get { return (DataTemplate)GetValue(HeaderTemplateProperty); }
            set { SetValue(HeaderTemplateProperty, value); }
        }

        // Using a DependencyProperty as the backing store for HeaderTemplate.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty HeaderTemplateProperty =
            DependencyProperty.Register("HeaderTemplate", typeof(DataTemplate), typeof(PivotItemsControl), new PropertyMetadata(null));



        protected override void PrepareContainerForItemOverride(DependencyObject element, object item)
        {
            base.PrepareContainerForItemOverride(element, item);
            
            var pi = element as PivotItem;
            if(pi == null)
                return;
            pi.Content = item;
            var asHeader = item as IHaveHeader;
            if (asHeader != null)
                pi.Header = asHeader.Header;
            else
            {
                pi.Header = item;
            }
            if (object.Equals(SelectedItem, item))
                pi.IsSelected = true;
            
        }



    }
}
